﻿#region Copyright Statement
//////////////////////////////////////////////
//      Created By:   Ali Ghomi             //
//      Date:         21 Jan 2010           //
//////////////////////////////////////////////
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Threading.Tasks;
namespace ORMAC.Core
{
    /// <summary>
    /// helper class to convert xml data based on bureau attributes
    /// This class contains the core conversion methods
    /// </summary>
    internal class LoaderBase
    {
        #region Protected Members
        protected const string idString = "_Id";
        protected string mainTableName=string.Empty;
        public DataSet dataSet;
        protected IEnumerable<TableInfo> allTables;
        #endregion
        #region Helpers
        /// <summary>
        /// helper to convert a date time row from table to datetime
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        protected DateTime? ConvertStructureDateTime(DataRow item)
        {
            if (item != null && item.Table.Columns.Contains("Year"))
                return new DateTime(int.Parse((string)item["Year"]), int.Parse((string)item["Month"]), int.Parse((string)item["Day"]));
            return null;
        }
        /// <summary>
        /// finds an item in specified table using row id
        /// </summary>
        /// <param name="tableName">Table Name</param>
        /// <param name="idColName">id coloumn name</param>
        /// <param name="id">row id</param>
        /// <returns></returns>
        protected virtual DataRow FindItem(string tableName, string idColName, int id)
        {
            if (string.IsNullOrEmpty(tableName) || !FindTable(tableName) || dataSet.Tables[tableName].Rows.Count == 0)
                return null;
            else
            {
                if (string.IsNullOrEmpty(idColName))
                    return dataSet.Tables[tableName].Rows[0];
                else if (!dataSet.Tables[tableName].Columns.Contains(idColName))
                    return null;
                else if (dataSet.Tables[tableName].Select().Any<DataRow>(r => !(r[idColName] is DBNull) && (int)r[idColName] == id))
                    return dataSet.Tables[tableName].Select().First<DataRow>(r => !(r[idColName] is DBNull) && (int)r[idColName] == id);
                else
                    return null;
            }
        }
        /// <summary>
        /// Finds items in specified table using the criteria
        /// </summary>
        /// <param name="tableName">Table Name</param>
        /// <param name="criteria">Selection criteria</param>
        /// <returns></returns>
        protected List<DataRow> FindItems(string tableName, string criteria,IEnumerable<string> primaryKey)
        {
            if (string.IsNullOrEmpty(tableName) || !FindTable(tableName) || dataSet.Tables[tableName].Rows.Count == 0)
                return null;
            else
            {
                HashSet<string> visited = new HashSet<string>();
                var res=dataSet.Tables[tableName].Select(criteria).ToList<DataRow>();
                if (primaryKey==null )
                    return res;
                List<DataRow> res1 = new List<DataRow>();
                //only return unique rows based on primary key of the table
                foreach (var item in res)
                {
                    var uniqueID = item.GetUniqueID(primaryKey);
                    if (!visited.Contains(uniqueID))
                    {
                        res1.Add(item);
                        visited.Add(uniqueID);
                    }
                }
                return res1;
            }
        }
        /// <summary>
        /// Generic method to  convert bureau classes
        /// will convert the items in one table to a list of objects. the type of the objects is retrieved from 
        /// the TableAttribute of the selected type. column names are retrieved from the Field Attributes on properties
        /// </summary>
        /// <typeparam name="T">Class Type</typeparam>
        /// <param name="dataSet">input data set</param>
        /// <param name="schema">Bureau</param>
        /// <returns>list of objects</returns>
        public virtual List<T> GetList<T>(DataSet dataSet, string schema,bool onlyValueObjects = false)
            where T : new()
        {
            string tableName = ReflectionHelper.TableName<T>(schema);
            if (!string.IsNullOrEmpty(tableName))
                return GetListWithTableName<T>(dataSet, schema, ReflectionHelper.TableName<T>(schema),null,onlyValueObjects);
            else
            {
                return null;
            }
        }
        /// <summary>
        /// Generic method to  convert bureau classes
        /// will convert the items in one table to a list of objects. the name of the input table is specified in the table name parameter and not the TableName attribute of the class
        /// . column names are retrieved from the Field Attributes on properties
        /// </summary>
        /// <typeparam name="T">Class Type</typeparam>
        /// <param name="dataSet">input data set</param>
        /// <param name="Bureau">Bureau</param>
        /// <param name="tableName">Input table name</param>
        /// <returns>list of objects</returns>

        protected virtual List<T> GetListWithTableName<T>(DataSet dataSet, string schema, string propertyTableName, string criteria = null, bool onlyValueObjects = false)
            where T : new()
        {
            string tableName = string.IsNullOrEmpty(mainTableName) ? propertyTableName : mainTableName;
            TableInfo currTable = allTables != null ? allTables.First<TableInfo>(t => t.Name == propertyTableName) : null;
            IEnumerable<string> primaryKey = ReflectionHelper.GetPrimaryKeys<T>(propertyTableName, schema);
            if (string.IsNullOrEmpty(tableName) || !FindTable(tableName))
                return null;
            if (string.IsNullOrEmpty(criteria))
                criteria = ReflectionHelper.TableCriteria<T>(schema);
            var items = FindItems(tableName, criteria,primaryKey);
            if (items == null)
                return null;
            List<T> res = new List<T>();
            res.Capacity=items.Count;
            Parallel.ForEach<DataRow>(items, item =>
            {
                if (!IsNull(currTable, item))
                {
                    var citem=ConvertItem2Object<T>(schema, item, propertyTableName, criteria, onlyValueObjects);
                    lock (items)
                        res.Add(citem);
                }
            });
            return res;
        }
        /// <summary>
        /// Checks if a table (xml elemnt) exists
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Bureau"></param>
        /// <returns></returns>
        protected virtual bool CheckExists<T>(string Bureau)
        {
            string tableName = ReflectionHelper.TableName<T>(Bureau);
            return !string.IsNullOrEmpty(tableName) && FindTable(tableName);
        }
        /// <summary>
        /// Retrieves class information from XML
        /// </summary>
        /// <typeparam name="T">Class Type</typeparam>
        /// <param name="Bureau">Bureau</param>
        /// <param name="onlyValueObjects">only convert value members (including strings)</param>
        /// <returns>object of T type</returns>
        public virtual T ConvertClass<T>(string Bureau, bool onlyValueObjects = false)
            where T : new()
        {
            string tableName = ReflectionHelper.TableName<T>(Bureau);
            DataRow item = null;
            if (!string.IsNullOrEmpty(tableName) && FindTable(tableName) && dataSet.Tables[tableName].Rows.Count == 1)
                item = dataSet.Tables[tableName].Rows[0];
            return ConvertItem2Object<T>(Bureau, item, tableName, null, onlyValueObjects);
        }
        /// <summary>
        /// Generic method to convert a single data item
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="schema"></param>
        /// <param name="item"></param>
        /// <param name="tableName"></param>
        /// <returns></returns>
        protected virtual T ConvertItem2Object<T>(string schema, DataRow item, string tableName, string prefix = null, bool onlyValueObjects = false)
            where T : new()
        {
            //Create the class
            var data = new T();
            //if current row is not null retrieve the id of the selected row
            int? ID = !string.IsNullOrEmpty(tableName) ? item.GetInt(tableName + idString) : 0;
            //otherwise set id to 0
            if (ID == null)
                ID = 0;
            //start converting class properties
            foreach (var propertyInfo in typeof(T).GetProperties())
            {
                //Get Property table name
                string propertyTableName =ReflectionHelper.TableName<T>(schema, propertyInfo.Name);

                //if prefix is specified in the container class and this property should use prefix
                //then add the prefix
                if (ReflectionHelper.UseTablePrefix<T>(schema, propertyInfo.Name))
                    propertyTableName = prefix + propertyTableName;
                //Get the property column name
                string propertyFieldName = ReflectionHelper.FieldName<T>(schema, propertyInfo.Name);
                //if prefix is specified in the container class and this property should use prefix
                //then add the prefix
                if (ReflectionHelper.UseFieldPrefix<T>(schema, propertyInfo.Name))
                    propertyFieldName = prefix + propertyFieldName;
                string fieldPrefix = ReflectionHelper.Prefix<T>(schema, propertyInfo.Name);
                string PropertyIntermediateTableName = ReflectionHelper.IntermediateTableName<T>(schema, propertyInfo.Name);
                //don't convert the properties which don't have mappings defined
                if (string.IsNullOrEmpty(propertyTableName) && string.IsNullOrEmpty(propertyFieldName))
                    continue;
                #region get the child element if intermediate table present
                Func<DataRow> GetChildFromIntermediateTable = new Func<DataRow>(() =>
                {
                    if (!string.IsNullOrEmpty(PropertyIntermediateTableName))
                    {
                        DataRow childItem = null;
                        childItem = FindItem(propertyTableName, tableName + idString, (int)ID);
                        if (childItem != null)
                        {
                            int? tID = childItem.GetInt(propertyTableName + idString);
                            childItem = FindItem(PropertyIntermediateTableName, propertyTableName + idString, (int)tID);
                        }
                        return childItem;
                    }
                    else
                        return item;
                });
                #endregion

                //Handle special fields
                //if (propertyInfo.PropertyType == typeof(PurposeEnum) || propertyInfo.PropertyType == typeof(PurposeEnum?))
                //{
                //    propertyInfo.SetValue(data, GetEnquiryPupose(GetChildFromIntermediateTable()), null);
                //}
                //else if (propertyInfo.PropertyType == typeof(DateTime?) || propertyInfo.PropertyType == typeof(DateTime))
                //{
                //    propertyInfo.SetValue(data, GetDateFromItem(GetChildFromIntermediateTable(), propertyTableName, tableName, propertyFieldName), null);
                //}
                //If property is a list handle list reursively
                /*else */if (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(List<>) && propertyTableName != tableName && !onlyValueObjects)
                {
                    Type internalType = propertyInfo.PropertyType.GetGenericArguments()[0];
                    string colName = tableName + idString;
                    if (!FindTable(propertyTableName) || !dataSet.Tables[propertyTableName].Columns.Contains(colName))
                        continue;
                    string tCriteria = colName + "=" + ID;
                    MethodInfo methodInfo = this.GetType().GetMethod("GetListWithTableName",BindingFlags.Instance|BindingFlags.NonPublic);
                    MethodInfo method = methodInfo.MakeGenericMethod(new Type[] { internalType });
                    object value = method.Invoke(this, new object[] { dataSet, schema, propertyTableName, tCriteria });
                    propertyInfo.SetValue(data, value, null);


                    //GetList<T>(dataSet, Bureau, propertyTableName);
                }
                else
                {
                    DataRow childItem = null;
                    if (!string.IsNullOrEmpty(propertyTableName) && propertyTableName != tableName && !string.IsNullOrEmpty(tableName))
                    {
                        if (!string.IsNullOrEmpty(PropertyIntermediateTableName))
                        {
                            childItem = GetChildFromIntermediateTable();
                            if (childItem == null)
                                continue;
                        }
                        else
                        {
                            childItem = FindItem(propertyTableName, tableName + idString, (int)ID);
                            if (childItem == null)
                                continue;
                        }
                    }
                    if (string.IsNullOrEmpty(tableName))//just use property table and not the parent class table
                    {
                        childItem = FindItem(propertyTableName, null, 0);
                        if (childItem == null)
                            continue;
                    }
                    //if the property is a class then convert the class recursively
                    if (propertyInfo.PropertyType.IsClass && propertyInfo.PropertyType != typeof(string))
                    {
                        if (!onlyValueObjects)
                        {
                            MethodInfo methodInfo = this.GetType().GetMethod("ConvertItem2Object",BindingFlags.Instance| BindingFlags.NonPublic);
                            MethodInfo method = methodInfo.MakeGenericMethod(new Type[] { propertyInfo.PropertyType });
                            object value = method.Invoke(this, new object[] { schema, childItem ?? item, propertyTableName, fieldPrefix, onlyValueObjects });
                            propertyInfo.SetValue(data, value, null);
                        }
                    }
                    else//if this is a simple property then convert it
                    {
                        propertyInfo.SetValue(data, childItem == null ? item.GetValue(propertyFieldName, propertyInfo.PropertyType, schema) : childItem.GetValue(propertyFieldName, propertyInfo.PropertyType, schema), null);
                    }
                }
            }
            return data;

        }

        protected bool FindTable(string name)
        {
            foreach (DataTable table in dataSet.Tables)
                if (table.TableName.ToLower() == name.ToLower())
                    return true;
            return false;
        }

        /// <summary>
        /// checks if all the <paramref name="currTable"/> columns are null
        /// </summary>
        /// <param name="currTable"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        protected bool IsNull(TableInfo currTable, DataRow item)
        {
            if (currTable==null || item == null)
                return true;
            foreach (var col in currTable.Columns)
                if (item[col.DatasetName] != DBNull.Value)
                    return false;
            return true;
        }

        #endregion


    }
}
